共计 4982 个字符,预计需要花费 13 分钟才能阅读完成。
引入
1. 什么 反射
- 反射就是通过字符串来操作类或者对象的属性
- Python 中一切皆对象, 即都可以使用反射
2. 反射的四个内置函数
反射的本质就是在使用内置函数, 其中反射有以下四个内置函数
-
hasattr : 判断一个属性或方法是否存在这个类中, 返回 bool 值
-
getattr : 获取属性值或者获取方法变量的内存地址 (根据字符串去获取 obj 对象里的对应的方法的内存地址 (加括号 "()" 就可以调用))
-
setattr : 给类或对象设置属性或方法 (通过 setattr 将外部的一个属性或函数绑定到实例中)
-
delattr : 删除类或对象的属性和方法
ps : getattr, hasattr, setattr, delattr 对模块的修改都在内存中进行,并不会影响文件中真实内容
一. 反射模块
1. 模块导入到模块动态导入
- 之前我们使用模块都是使用 import 的方式进行导入
🍔文件 test24.py
def Foo1():
print("i am test24_Foo1")
def Foo2():
print("i am test24_Foo2")
def Foo3():
print("i am test24_Foo3")
🍔文件 test 反射.py
import test24
test24.Foo1() # i am test24_Foo1
test24.Foo2() # i am test24_Foo2
test24.Foo3() # i am test24_Foo3
- 通过反射模块的方式动态的导入模块, Python 提供了
__import__
来实现这一功能
🍔文件 test 反射.py
Inp = input(" 请输入想要导入的模块 >>").strip()
mode = __import__(Inp) # 通过字符串的方式导入你想要的导的模块
mode.Foo1()
mode.Foo2()
mode.Foo3()
''' 输出
请输入想要导入的模块 >>test24
i am test24_Foo1
i am test24_Foo2
i am test24_Foo3
'''
- 问题 : 当我们的模块文件路径与执行文件不在同一级, 如下图所示
🍔文件 setting.py
def Foo1():
print("i am setting_Foo1")
🍔文件 test 反射.py
Inp = input(" 请输入想要导入的模块 >>").strip()
mode = __import__(Inp)
mode.Foo1()
''' 输出
请输入想要导入的模块 >>conf.setting
抛出异常 "AttributeError: module 'conf' has no attribute 'Foo1'"
'''
- 解决方法
Inp = input(" 请输入想要导入的模块 >>").strip()
mode = __import__(Inp,fromlist=True) # 添加参数
mode.Foo1()
''' 输出
请输入想要导入的模块 >>conf.setting
i am setting_Foo1
'''
2. 第二种反射模块方法
- 使用
importlib
模块实现 (在 Python3 框架中使用的比较多)
🍔文件 test26.py
def test():
print("i am test26")
🍔文件 test 反射模块.py
import importlib
Inp = input(" 请输入你要导入的模块 >>").strip()
mode = importlib.import_module(Inp)
mode.test()
''' 输出
请输入你要导入的模块 >>test26
i am test26
'''
- 当我们的模块文件路径与执行文件 不在同一级, 如下图所示
🍔文件 setting.py
def Foo1():
print("i am setting_Foo1")
import importlib
Inp = input(" 请输入你要导入的模块 >>").strip()
mode = importlib.import_module(Inp)
mode.Foo1()
''' 输出
请输入你要导入的模块 >>conf.setting
i am setting_Foo1
'''
二.hasattr(obj,name)
- hasattr : 判断一个属性或方法是否存在这个类中, 返回 bool 值
- hasattr是通过调用
getattr(ojbect, name)
是否抛出异常来实现的
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def fight(self):
print(" 正在 fight")
P1 = Person(" 小王 ",18)
print(hasattr(P1,"name")) # True
print(hasattr(P1,"age")) # True
print(hasattr(P1,"fight")) # True
print(hasattr(P1,"aaaa")) # False
三.getattr(obj,name)
- getattr : 获取属性值或者获取方法变量的内存地址
- 根据字符串去获取 obj 对象里的对应的方法的 内存地址 (加括号 "()" 就可以调用)
🍔续上面模块动态导入补充
Inp = input(" 请输入想要导入的模块 >>").strip()
mode = __import__(Inp,fromlist=True)
method = input(" 请输入你想要执行的方法 >>").strip()
print(getattr(mode,method,None)) # 当在模块中没有找到该方法时, 返回 None, 找到时返回该方法的内存地址
'''🔰存在 -- 输出
请输入想要导入的模块 >>conf.setting
请输入你想要执行的方法 >>Foo1
<function Foo1 at 0x00000214B2964438>
''''''🔰不存在 --- 输出
请输入想要导入的模块 >>conf.setting
请输入你想要执行的方法 >>aaa
None
'''
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def fight(self):
print(" 正在 fight")
return 123
P1 = Person(" 小王 ",18)
print(getattr(P1,"name",None)) # 小王
print(getattr(P1,"aaaa",None)) # None
# 如果对象中有 "name" 属性, 则打印 "self.name" 的值, 否则打印 "None"
print(getattr(P1,"fight",None)) # [方法的内存地址]
# 如果对象中有 "fight" 方法, 返回该方法的内存地址, 否则返回 "None"
print(getattr(P1,"fight",None)()) # 正在 fight 123
# 加了括号, 存在该方法的话运行该方法并返回该方法的返回值, 否则返回 "None"
print("name" in P1.__dict__) # True
# 也可以通过 "__dict__" 来进行判断, 效果一样
四.setattr(obj,name,value)
- setattr : 给类或对象设置属性或方法
- 通过 setattr 将外部的一个属性或函数绑定到实例中
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def fight(self):
print(" 正在 fight")
return 123
P1 = Person(" 小王 ",18)
# 设置属性
setattr(P1,"sex","man")
print(getattr(P1,"sex",None)) # man
# 设置方法
def runrun(self):
print(" 正在跑 ")
setattr(Person,"run",runrun)
getattr(P1,"run",None)() # 正在跑
# 注意 : 绑定给对象的方法是放在类里面的, 如果使用反射, 则要反射到类的内部
# 如果要将方法反射给对象, 那么就需要手动传入对象 (不推荐这么使用)
def rrr(obj):
print(obj.name)
setattr(P1,"print_name",rrr)
P1.print_name(P1) # 小王
五.delattr(obj,name)
- delattr : 删除类或对象的属性和方法
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def fight(self):
print(" 正在 fight")
return 123
P1 = Person(" 小王 ",18)
delattr(P1,"name")
print(P1.__dict__) # {'age': 18}
getattr(P1,"name") # 报错 "AttributeError" 没有该属性
六. 应用小示例
1. 给空类动态设置属性
class Extend:
pass
E1 = Extend()
Inp1 = input(" 请输入你需要设置的属性名 >>").strip()
Inp2 = input(" 请输入该属性对应的值 >>").strip()
setattr(E1,Inp1,Inp2) # 设置属性
print(getattr(E1,Inp1,None))
''' 输出
请输入你需要设置的属性名 >>name
请输入该属性对应的值 >>shawn
shawn
'''
2. 反射的应用案例
class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def print_name(self):
print(f" 名字:{self.name}")
def print_age(self):
print(f" 年龄:{self.age}")
def print_sex(self):
print(f" 性别:{self.sex}")
def print_info(self):
print(self.name,self.age,self.sex)
def select(self):
while 1:
Inp = input(" 请输入需要选择的方法(q 退出)>>").strip()
if Inp.lower() == "q":break
if hasattr(self,Inp): # 判断存不存在
getattr(self,Inp)() # 拿到内存地址加括号调用
else:
print(" 没有该功能 ")
P1 = Person(" 久人旅 ",23,"man")
P1.select()
''' 输出
请输入需要选择的方法(q 退出)>>aaaa
没有该功能
请输入需要选择的方法(q 退出)>>print_name
名字: 久人旅
请输入需要选择的方法(q 退出)>>print_age
年龄:23
请输入需要选择的方法(q 退出)>>print_sex
性别:man
请输入需要选择的方法(q 退出)>>q
Process finished with exit code 0
'''
3. 反射隐藏属性
- 前面 类的封装 那一篇我们已经介绍过了, 设置隐藏属性的时候会进行变形操作,下面我们与反射组合使用
class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.__sex = sex # 变形 "_Person__sex"
P1 = Person(" 闫帝喵 ",23,"man")
🍔通过反射获取隐藏属性
print(getattr(P1,"__sex")) # 报错 "AttributeError" 没有该属性
print(getattr(P1,"_Person__sex")) # man
🍔通过反射设置隐藏属性(为对象设置)
setattr(P1,"_Person__money",4654)
print(getattr(P1,"__money")) # 报错 "AttributeError" 没有该属性
print(getattr(P1,"_Person__money")) # 4654
🍔通过反射设置隐藏属性(为类设置)
setattr(Person,"_Person__money",13213)
print(getattr(P1,"__money")) # 报错 "AttributeError" 没有该属性
print(getattr(P1,"_Person__money")) # 13213
正文完